// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: fengyang

#pragma once

#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <vector>

#include <boost/asio.hpp>

#include "cc/coin2/feed/mgr/system.h"
#include "coin2/exchange/base/feed/timer/timer.h"
#include "coin2/exchange/base/feed/timer/timer_subscriber.h"

namespace coin2::exchange::base::feed::timer {
namespace impl {

using exchange::base::feed::IFeedSubscriber;
class ArchiveTimerManager;
class LiveTimerManager;

struct TimerProperty{
  bool periodic;
  int64_t duration;
  int64_t timestamp;
  std::function<void(void)> callback;
};

class TimerManager {
 public:
  virtual ~TimerManager();

 protected:
  TimerManager();
  // init/delete are in feed system initalization, used in single thread only
  static std::shared_ptr<ArchiveTimerManager> CreateArchiveManager();
  static std::shared_ptr<LiveTimerManager> CreateLiveManager();
  static std::shared_ptr<TimerManager> GetInstance();
  static void DeleteInstance();

  virtual int64_t ExpiresAt(uint64_t id, int64_t timestamp, std::function<void(void)> callback) = 0;
  virtual int64_t ExpiresFromNow(uint64_t id, int64_t duration, std::function<void(void)> callback) = 0;
  virtual int64_t ExpiresEvery(uint64_t id, int64_t duration, std::function<void(void)> callback) = 0;
  virtual void Cancel(uint64_t id) = 0;
  virtual void Tick(int64_t now) = 0;
  // live mode, return absolute expiry time
  // archive mode, if no market feed data replay yet at beginning, return the setting time,
  // once has received feed data, return absolute expiry time
  virtual int64_t Expires(uint64_t id);

  uint64_t NewTimeId();
  void AddTimer(int64_t now, uint64_t id, const TimerProperty& property);
  void RemoveTimer(uint64_t id);
  void TriggerTimer();
  void CheckExpires(int64_t now);

  uint64_t timer_id_ = 0;
  // <timestamp, timer_id>
  std::set<std::pair<int64_t, uint64_t>> timers_;
  std::map<uint64_t, TimerProperty> timer_property_;
  std::map<uint64_t, TimerProperty> pending_timers;

  IFeedSubscriber* subscriber_;
  FeedTimer feed_timer_;

  static std::shared_ptr<TimerManager> instance_;

  friend class DeadlineTimer;
  friend class coin2::feed::mgr::FeedSubsystemManager;
  friend class TimerFeedSubscriber;
};

class ArchiveTimerManager: public TimerManager {
 public:
  virtual ~ArchiveTimerManager();

 protected:
  ArchiveTimerManager();
  int64_t ExpiresAt(uint64_t id, int64_t timestamp, std::function<void(void)> callback) override;
  int64_t ExpiresFromNow(uint64_t id, int64_t duration, std::function<void(void)> callback) override;
  int64_t ExpiresEvery(uint64_t id, int64_t duration, std::function<void(void)> callback) override;
  void Cancel(uint64_t id) override;
  void Tick(int64_t now) override;
  int64_t Expires(uint64_t id) override;

  IFeedSubscriber* HookSubscriber(IFeedSubscriber* subscriber);

  std::optional<int64_t> now_;
  std::unique_ptr<TimerFeedSubscriber> timer_subscriber_;

  friend class TimerManager;
  friend class coin2::feed::mgr::FeedSubsystemManager;
};

class LiveTimerManager : public TimerManager {
 public:
  virtual ~LiveTimerManager();

 protected:
  LiveTimerManager();
  int64_t ExpiresAt(uint64_t id, int64_t timestamp, std::function<void(void)> callback) override;
  int64_t ExpiresFromNow(uint64_t id, int64_t duration, std::function<void(void)> callback) override;
  int64_t ExpiresEvery(uint64_t id, int64_t duration, std::function<void(void)> callback) override;
  void Cancel(uint64_t id) override;
  void Tick(int64_t now) override;

  void RegisterSubscriber(IFeedSubscriber* subscriber);
  void RegisterTimerIoc(boost::asio::io_context& ioc);
  void ResetTimerExpiry();
  void TimerCallback();

  std::unique_ptr<boost::asio::deadline_timer> tick_timer_;

  friend class TimerManager;
  friend class coin2::feed::mgr::FeedSubsystemManager;
};

}  // namespace impl

using impl::TimerManager;
using impl::ArchiveTimerManager;
using impl::LiveTimerManager;

}  // namespace coin2::exchange::base::feed::timer
